/***************************************************
 This is a library for the MCP23017 i2c port expander

 These displays use I2C to communicate, 2 pins are required to
 interface
 Adafruit invests time and resources providing this open source code,
 please support Adafruit and open-source hardware by purchasing
 products from Adafruit!

 Written by Limor Fried/Ladyada for Adafruit Industries.
 BSD license, all text above must be included in any redistribution
 ****************************************************/


#include "MCP23017.h"


/**
 * Bit number associated to a give Pin
 */
uint8_t MCP23017::bitForPin(uint8_t pin)
{
    return pin % 8;
}

/**
 * Register address, port dependent, for a given PIN
 */
uint8_t MCP23017::regForPin(uint8_t pin, uint8_t portAaddr, uint8_t portBaddr)
{
    return(pin < 8) ? portAaddr : portBaddr;
}

/**
 * Reads a given register
 */
uint8_t MCP23017::readRegister(uint8_t addr)
{
    // read the current GPINTEN

    //	Wire.beginTransmission(MCP23017_ADDRESS | i2caddr);
    //	wiresend(addr);
    //	Wire.endTransmission();
    //	Wire.requestFrom(MCP23017_ADDRESS | i2caddr, 1);
    //	return wirerecv();
    uint8_t value;
    i2c->take_i2c_right(speed);
    i2c->read_byte(MCP23017_ADDRESS | i2caddr, addr, &value);
    i2c->release_i2c_right();
    return value;

}


/**
 * Writes a given register
 */
void MCP23017::writeRegister(uint8_t regAddr, uint8_t regValue)
{
    // Write the register
    //	Wire.beginTransmission(MCP23017_ADDRESS | i2caddr);
    //	wiresend(regAddr);
    //	wiresend(regValue);
    //	Wire.endTransmission();
    i2c->take_i2c_right(speed);
    i2c->write_byte(MCP23017_ADDRESS | i2caddr, regAddr, regValue);
    i2c->release_i2c_right();
}


/**
 * Helper to update a single bit of an A/B register.
 * - Reads the current register value
 * - Writes the new register value
 */
void MCP23017::updateRegisterBit(uint8_t pin, uint8_t pValue, uint8_t portAaddr, uint8_t portBaddr)
{
    uint8_t regValue;
    uint8_t regAddr = regForPin(pin, portAaddr, portBaddr);
    uint8_t bit = bitForPin(pin);
    regValue = readRegister(regAddr);

    // set the value for the particular bit
    bit_write(regValue, bit, pValue);

    writeRegister(regAddr, regValue);
}

////////////////////////////////////////////////////////////////////////////////

/**
 * Initializes the MCP23017 given its HW selected address, see datasheet for Address selection.
 */
void MCP23017::begin(uint32_t speed, uint8_t addr)
{
    if (addr > 7)
    {
        addr = 7;
    }
    i2caddr = addr;
    this->speed = speed;

    i2c->begin(speed);

    // set defaults!
    // all inputs on port A and B
    writeRegister(MCP23017_IODIRA, 0xff);
    writeRegister(MCP23017_IODIRB, 0xff);
}

/**
 * Initializes the default MCP23017, with 000 for the configurable part of the address
 */
void MCP23017::begin(void)
{
    begin(400000, 0);
}

/**
 * Sets the pin mode to either INPUT or OUTPUT
 */
void MCP23017::pinMode(uint8_t p, uint8_t d)
{
    updateRegisterBit(p, (d == INPUT), MCP23017_IODIRA, MCP23017_IODIRB);
}

/**
 * Reads all 16 pins (port A and B) into a single 16 bits variable.
 */
uint16_t MCP23017::readGPIOAB()
{
    //	uint16_t ba = 0;
    //	uint8_t a;

    // read the current GPIO output latches
    //	Wire.beginTransmission(MCP23017_ADDRESS | i2caddr);
    //	wiresend(MCP23017_GPIOA);
    //	Wire.endTransmission();
    //

    //	Wire.requestFrom(MCP23017_ADDRESS | i2caddr, 2);
    //	a = wirerecv();
    //	ba = wirerecv();
    //	ba <<= 8;
    //	ba |= a;

    //	return ba;

    DataU16_t data;
    i2c->take_i2c_right(speed);
    i2c->read_byte(MCP23017_ADDRESS | i2caddr, MCP23017_GPIOA, data.byte, 2);
    i2c->release_i2c_right();

    return data.value;


}

/**
 * Read a single port, A or B, and return its current 8 bit value.
 * Parameter b should be 0 for GPIOA, and 1 for GPIOB.
 */
uint8_t MCP23017::readGPIO(uint8_t b)
{

    // read the current GPIO output latches
    //	Wire.beginTransmission(MCP23017_ADDRESS | i2caddr);
    //	if (b == 0)
    //		wiresend(MCP23017_GPIOA);
    //	else {
    //		wiresend(MCP23017_GPIOB);
    //	}
    //	Wire.endTransmission();

    //	Wire.requestFrom(MCP23017_ADDRESS | i2caddr, 1);
    //	return wirerecv();

    uint8_t data;
    i2c->take_i2c_right(speed);
    if (b == 0)
        i2c->read_byte(MCP23017_ADDRESS | i2caddr, MCP23017_GPIOA, &data);
    else
        i2c->read_byte(MCP23017_ADDRESS | i2caddr, MCP23017_GPIOB, &data);
    i2c->release_i2c_right();

    return data;

}

/**
 * Writes all the pins in one go. This method is very useful if you are implementing a multiplexed matrix and want to get a decent refresh rate.
 */
void MCP23017::writeGPIOAB(uint16_t ba)
{
    //	Wire.beginTransmission(MCP23017_ADDRESS | i2caddr);
    //	wiresend(MCP23017_GPIOA);
    //	wiresend(ba & 0xFF);
    //	wiresend(ba >> 8);
    //	Wire.endTransmission();
    DataU16_t data;
    data.value = ba;
    i2c->take_i2c_right(speed);
    i2c->write_byte(MCP23017_ADDRESS | i2caddr, MCP23017_GPIOA, data.byte, 2);
    i2c->release_i2c_right();
}

void MCP23017::digitalWrite(uint8_t pin, uint8_t d)
{
    uint8_t gpio;
    uint8_t bit = bitForPin(pin);


    // read the current GPIO output latches
    uint8_t regAddr = regForPin(pin, MCP23017_OLATA, MCP23017_OLATB);
    gpio = readRegister(regAddr);

    // set the pin and direction
    bit_write(gpio, bit, d);

    // write the new GPIO
    regAddr = regForPin(pin, MCP23017_GPIOA, MCP23017_GPIOB);
    writeRegister(regAddr, gpio);
}

void MCP23017::pullUp(uint8_t p, uint8_t d)
{
    updateRegisterBit(p, d, MCP23017_GPPUA, MCP23017_GPPUB);
}

uint8_t MCP23017::digitalRead(uint8_t pin)
{
    uint8_t bit = bitForPin(pin);
    uint8_t regAddr = regForPin(pin, MCP23017_GPIOA, MCP23017_GPIOB);
    return (readRegister(regAddr) >> bit) & 0x1;
}

/**
 * Configures the interrupt system. both port A and B are assigned the same configuration.
 * Mirroring will OR both INTA and INTB pins.
 * Opendrain will set the INT pin to value or open drain.
 * polarity will set LOW or HIGH on interrupt.
 * Default values after Power On Reset are: (false,flase, LOW)
 * If you are connecting the INTA/B pin to arduino 2/3, you should configure the interupt handling as FALLING with
 * the default configuration.
 */
void MCP23017::setupInterrupts(uint8_t mirroring, uint8_t openDrain, uint8_t polarity)
{
    // configure the port A
    uint8_t ioconfValue = readRegister(MCP23017_IOCONA);
    bit_write(ioconfValue, 6, mirroring);
    bit_write(ioconfValue, 2, openDrain);
    bit_write(ioconfValue, 1, polarity);
    writeRegister(MCP23017_IOCONA, ioconfValue);

    // Configure the port B
    ioconfValue = readRegister(MCP23017_IOCONB);
    bit_write(ioconfValue, 6, mirroring);
    bit_write(ioconfValue, 2, openDrain);
    bit_write(ioconfValue, 1, polarity);
    writeRegister(MCP23017_IOCONB, ioconfValue);
}

/**
 * Set's up a pin for interrupt. uses arduino MODEs: CHANGE, FALLING, RISING.
 *
 * Note that the interrupt condition finishes when you read the information about the port / value
 * that caused the interrupt or you read the port itself. Check the datasheet can be confusing.
 *
 */
void MCP23017::setupInterruptPin(uint8_t pin, uint8_t mode)
{

    // set the pin interrupt control (0 means change, 1 means compare against given value);
    updateRegisterBit(pin, (mode != CHANGE), MCP23017_INTCONA, MCP23017_INTCONB);
    // if the mode is not CHANGE, we need to set up a default value, different value triggers interrupt

    // In a RISING interrupt the default value is 0, interrupt is triggered when the pin goes to 1.
    // In a FALLING interrupt the default value is 1, interrupt is triggered when pin goes to 0.
    updateRegisterBit(pin, (mode == FALLING), MCP23017_DEFVALA, MCP23017_DEFVALB);

    // enable the pin for interrupt
    updateRegisterBit(pin, HIGH, MCP23017_GPINTENA, MCP23017_GPINTENB);

}

uint8_t MCP23017::getLastInterruptPin()
{
    uint8_t intf;

    // try port A
    intf = readRegister(MCP23017_INTFA);
    for(int i = 0; i < 8; i++) if (bit_read(intf, i)) return i;

    // try port B
    intf = readRegister(MCP23017_INTFB);
    for(int i = 0; i < 8; i++) if (bit_read(intf, i)) return i + 8;

    return MCP23017_INT_ERR;

}
uint8_t MCP23017::getLastInterruptPinValue()
{
    uint8_t intPin = getLastInterruptPin();
    if(intPin != MCP23017_INT_ERR)
    {
        uint8_t intcapreg = regForPin(intPin, MCP23017_INTCAPA, MCP23017_INTCAPB);
        uint8_t bit = bitForPin(intPin);
        return (readRegister(intcapreg) >> bit) & (0x01);
    }

    return MCP23017_INT_ERR;
}


